8.3 关键字:using

using定义类型别名

C++11新标准引入了using关键字来定义类型别名(传统方法是用typedef定义类型别名):

using Dict = std::map<std::string, std::string>;
Dict dict;  // dict的类型是map<string, string> 

命名空间的using声明

1. using声明命名空间内的成员

使用命名空间的using声明后,我们使用命名空间内的成员时就无须加上命名空间的前缀了,例如:

#include <iostream>

int main(void) {
    using std::cout;
    using std::endl;

    cout << "tomocat" << endl;
}

// 输出:
tomocat

2. using声明整个命名空间(不推荐)

当然,我们也可以用using直接声明整个命名空间,这样使用命名空间内的所有成员都无须加上前缀了,例如:

Tips:不推荐用using声明整个命名空间, 这样会引入该命名空间内的所有成员,如果有和用户代码重名的成员可能引发非预期的错误。

#include <iostream>

int main(void) {
    using namespace std;  // 不推荐的写法

    cout << "tomocat" << endl;
}

3. 头文件不要包含命名空间的using声明

在头文件中包含了命名空间的using声明会导致所有引入该头文件的文件都包含这个声明,可能会产生非预期的命名冲突。

派生类改变个别成员的可访问性

有时我们需要改变派生类继承的某个名字的访问级别,通过使用using声明可以实现这一目的:

class Base {
 public:
    std::size_t size() const { return n; }
 protected:
    std::size_t n;
};

// private继承: 继承而来的成员是私有成员
class SubClass : private Base {
 public:
    using Base::size;
 protected:
    using Base::n;
};

因为SubClass使用了私有继承,所以继承而来的成员sizen默认是派生类SubClass的私有成员,然而我们使用using声明语句改变了这些成员的可访问性。改变之后SubClass的用户将可以使用size成员,而SubClass的派生类可以使用n

Tips:派生类只能为那些它可以访问(非私有成员)的名字提供using声明。

避免遮掩继承而来的名称

Effective C++:Avoid hiding inherited names.

  • derived class内的名称会遮盖base class内的名称,在public继承下从来没有人希望如此。

  • 为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding funcitons)。

举个例子:

class Base {
 private:
    int x;
 
 public:
    virtual void mf1() = 0;
    virtual void mf1(int);
    virtual void mf2();
    void mf3();
    void mf3(double);
};

class Derived : public Base {
 public:
    virtual void mf1();
    void mf3();
    void mf4();
};

base class内所有名为mf1mf3的函数都被derived class内的mf1mf3函数都被遮掩掉了。从名称查找观点上看,Base::mf1Base::mf3不再被Derived class继承。

注意上述的遮掩规则即使base class和derived class内的函数有不同的参数类型都适用,而且不论函数是virtual还是non-virtual都适用。

Derived d;
int x;

d,mf1();   // 正确: 调用Derived::mf1
d.mf1(x);  // 错误: Derived::mf1遮掩了Base::mf1
d,mf2();   // 正确: 调用Base::mf2
d.mf3();   // 正确: 调用Derived::mf3
d.mf3(x);  // 错误: Derived::mf3遮掩了Base::mf3

你可以使用using声明式达成目标:

class Base {
 private:
    int x;
 
 public:
    virtual void mf1() = 0;
    virtual void mf1(int);
    virtual void mf2();
    void mf3();
    void mf3(double);
};

class Derived : public Base {
 public:
    // 让base class内名为mf1和mf3的所有东西都在Derived作用域内都可见并且public
    using Base::mf1;
    using Base::mf3;
    
    virtual void mf1();
    void mf3();
    void mf4();
};

值得一提的是,如果Derived以private的方式继承Base,而Derived唯一想继承的是Base::mf1的无参数版本。而using声明会令继承而来的某给定名称的所有同名函数在derived class中都可见。我们可以使用一个简单的转交函数。

class Base {
 private:
    int x;
 
 public:
    virtual void mf1() = 0;
    virtual void mf1(int);
    virtual void mf2();
    void mf3();
    void mf3(double);
};

class Derived : private Base {  // private 继承
 public:
    // 转交函数(forwarding function)
    virtual void mf1() {
        Base::mf1();
    }
};

Derived d;
int x;
d.mf1();   // 正确, 调用Derived::mf1
d.mf1(x);  // 错误: Base::mf1的有参版本被遮掩了

类模板的类型别名

C++11新标准允许我们为类模板定义一个类型别名:

// authors的类型是pair<string, string>
template<typename T> using twin = pair<T, T>;
twin<string> authors;

当我们定义一个模板类型别名时,也可以固定一个或多个模板参数:

template <typename T> using partNo = pair<T, unsigned>;
partNo<string> books;  // books类型是pair<string, unsigned>
partNo<Student> kids;  // kids类型是pair<Student, unsigned>